package com.jd.dlc.aspect;

import com.jd.dlc.annotation.DlcLimiter;
import com.jd.dlc.domain.WrapLimiterRule;
import com.jd.dlc.limit.Limiter;
import com.jd.dlc.limit.Trigger;
import com.jd.dlc.limit.biz.*;
import com.jd.dlc.limit.dynamic.DlcLimiterRuleFactory;
import com.jd.dlc.limit.dynamic.LimiterRuleDynamic;
import com.jd.dlc.limit.scan.Sanner;
import com.jd.dlc.limit.window.WindowLimiter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * @ClassName: FlowMonitorUtil
 * @Description: 流量监控整合
 * @Author: MengXiangXin
 * @Date: 2022/4/7 9:40 AM
 */

@Slf4j
@Aspect
@Component
public class LimiterAspect extends BAspect {

    @Resource
    private HttpServletResponse response;

    @Resource
    private LimiterRuleDynamic limiterRuleDynamic;

    @Resource
    private KeyProcessor keyFactory;


    @Resource
    private WindActurtor windActurtor;

    @Resource
    private PreCheck preCheck;

    @Resource
    private DownActuator downActuator;

    @Resource
    private Trigger trigger;

    //创建切点
    @Pointcut("@annotation(com.jd.dlc.annotation.DlcLimiter)")
    public void guaLog() {
    }

    /**
     * @Description: guava限流
     * @Date 2022/5/9 6:39 PM
     * @Param []
     * @Return void
     **/
    @Around("guaLog()")
    public Object guavaLimiter(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        try {
            /**
             * global switch
             */
            if (!DlcLimiterRuleFactory.limitEnable) {
                return joinPoint.proceed();
            }

            //获取拦截的方法名
            Signature sig = joinPoint.getSignature();

            //获取拦截的方法名
            MethodSignature msig = (MethodSignature) sig;

            //返回被织入增加处理目标对象
            Object target = joinPoint.getTarget();

            //为了获取注解信息
            Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());

            //获取注解信息
            DlcLimiter limiterAnno = currentMethod.getAnnotation(DlcLimiter.class);

            if (limiterAnno == null) {
                return joinPoint.proceed();
            }
            /**
             * The method name where the annotation is located distinguishes between different current limiting strategies
             */
            String namespace = limiterAnno.namespace() == null ? msig.getName() : limiterAnno.namespace();

            /**
             * EL parse
             */
            String limitKey = super.evalEL(joinPoint, limiterAnno.key());

            /**
             * get configuration
             */
            WrapLimiterRule wrapLimiterRule = limiterRuleDynamic.loadConfig(namespace, limitKey);

            if (!wrapLimiterRule.isEnable()) {
                return joinPoint.proceed();
            }

            /**
             * pre-check black white
             */
            LimiterBencher bencher = preCheck.onDo(args, wrapLimiterRule);

            if (bencher.isPassAll()) {
                return joinPoint.proceed();
            }

            if (bencher.isForbidden()) {
                return downActuator.isDisAllow(namespace, limitKey, joinPoint, response);
            }
            if (!bencher.isCheckAssert()) {
                return downActuator.isDisAllow(namespace, limitKey, joinPoint, response);
            }

            /**
             * params
             */
            limitKey = keyFactory.wrapperKey(limitKey, wrapLimiterRule);

            /**
             * Get the token added to the bucket every second by the annotation
             */
            Long limitNum = wrapLimiterRule.getLimit() == null ? limiterAnno.limit() : wrapLimiterRule.getLimit();

            Long timeOut = wrapLimiterRule.getTimeout() == null ? limiterAnno.timeOut() : wrapLimiterRule.getTimeout();

            if (limitNum.equals(Integer.MAX_VALUE)) {
                /**
                 * take the inside approach
                 */
                return joinPoint.proceed();
            }

            /**
             * Determine whether to limit the current SPI
             */
            Limiter limiter = Sanner.limits.get(namespace);

            if (limiter == null) {
                return joinPoint.proceed();
            }

            /**
             * Current limit check
             */
            boolean allowed = limiter.isAllowed(limitKey, timeOut, limitNum);

            /**
             * for sliding window
             * Settlement Custom Demand Extensions
             */
            if (limiter instanceof WindowLimiter) {
                return windActurtor.windowLimiter(namespace, limitKey,
                        bencher, wrapLimiterRule, joinPoint, allowed, response);
            }

            if (!allowed && wrapLimiterRule.isTriggerLimit()) {
                trigger.triggerLimiter(namespace, limitKey, joinPoint, response, null);
            } else {
                joinPoint.proceed();
            }

        } catch (Exception ex) {
            log.error("### important 限流触发异常 {} ars {}", ex, args);
            return joinPoint.proceed();
        }
        return null;
    }


}
